/**
 * 富欣测试报告类
 *
 * Created by snowingsea on 2020/11/15.
 */
const path = require('path');
const fs = require('fs-extra');
const moment = require('moment');
const compressing = require('compressing');
const { I18n } = require('i18n');

const BaseReporter = require('./../base-report');
const files = require('./../../../files');
const functions = require('./../../../functions');
const tests = require('./../../../tests');
const utility = require('./../../../utility');
const Excel = require('./../adapter/excel');
const Docx = require('./../adapter/docx');
const GlobalLocale = require('./../../../../locales/en-US.json');

const ReportTestCaseTransformer = require('./../base-report-test-case-transformer')();

class FuxinReport extends BaseReporter {
  constructor(projectVersionId, locale, projectVersion = null, project = null, creator = null) {
    super(projectVersionId, locale, projectVersion, project, creator);
    this.i18n = new I18n(
      {
        locales: ['en-US'],
        defaultLocale: 'en-US',
        staticCatalog: {
          'en-US': utility.deepMerge(
            {},
            GlobalLocale,
            utility.requireUncached(path.join(__dirname, './locales/en-US.json')),
          ),
        },
        objectNotation: true,
      },
    );
    this.i18n.setLocale('en-US');
    this.componentTestReportExcel = null;
    this.coverageReviewReportExcel = null;
    this.dynamicTestReportExcel = null;
    this.swDDSTraceTableExcel = null;
    this.globalTestTypeStatistics = {};
  }

  generate() {
    const result = {
      cachePath: this.outputDir,
    };
    return new Promise((resolve, reject) => {
      this.spliteByProjectVersion()
        .then(() => {
          const fileName = `${this.project.projectName}-${this.version.versionName}-单元测试报告.zip`;
          result.reportPath = path.join(this.outputDir, fileName);
          const reportsDir = path.join(this.outputDir, this.project.projectName);
          return compressing.zip.compressDir(reportsDir, result.reportPath);
        })
        .then(() => {
          resolve(result);
        })
        .catch(reject);
    });
  }

  /**
   * 按项目版本分割
   * @param {String} versionPath 版本目录
   * @returns {Promise}
   */
  handleVersion(versionPath) {
    const promises = [
      this.generateComponentTestReport(versionPath),
      this.generateCoverageReviewReport(versionPath),
      this.generateDynamicTestReport(versionPath),
      this.generateSwDDSTraceTableReport(versionPath),
      this.generateTestCasesReport(versionPath),
    ];
    return Promise.all(promises)
      .then(() => fs.writeJsonSync(path.join(versionPath, 'Test_Type_Statistics.txt'), this.globalTestTypeStatistics));
  }

  /**
   * 获取文件列表
   * @return {Promise}
   */
  getFiles() {
    return new Promise((resolve, reject) => {
      let extensions = [];
      Object.keys(this.version.languages).forEach((language) => {
        extensions = extensions.concat(this.version.languages[language].suffixes);
      });
      const filter = {
        kinds: [files.kind.file],
        extensions,
      };
      return files.getFiles(this.versionId, {}, filter)
        .then(resolve)
        .catch(reject);
    });
  }

  /**
   * 获取类型系统
   * @param {String} fileId 文件id
   * @return {Promise}
   */
  getTypes(fileId) {
    return files.getTypes(fileId);
  }

  /**
   * 获取函数列表
   * @return {Promise}
   */
  async getFunctions() {
    const funcs = await functions.getFunctions(this.versionId);
    funcs.forEach((func) => {
      func.sil = (func.userDefinedFields
        .find((udfObj) => udfObj.name === 'SIL') || {}).value;
      func.requirementId = (func.userDefinedFields
        .find((udfObj) => udfObj.name === 'udfSwDDS_ID') || {}).value;
      func.comment = (func.userDefinedFields
        .find((udfObj) => udfObj.name === 'comment') || {}).value;
    });
    return funcs;
  }

  /**
   * 获取某个文件的函数集合
   * @param {String} fileId 文件id
   * @return {Promise}
   */
  async getFunctionsOfFile(fileId) {
    const funcs = await functions.getFunctions(this.versionId, {}, [fileId]);
    funcs.forEach((func) => {
      func.sil = (func.userDefinedFields
        .find((udfObj) => udfObj.name === 'SIL') || {}).value;
      func.requirementId = (func.userDefinedFields
        .find((udfObj) => udfObj.name === 'udfSwDDS_ID') || {}).value;
    });
    return funcs;
  }

  /**
   * 获取某个文件的函数变量集合
   * @param {String} fileId 文件id
   * @return {Promise}
   */
  getFunctionVariablesOfFile(fileId) {
    return functions.getFunctionVariablesOfFile(fileId);
  }

  /**
   * 获取某个文件的测试用例集合
   * @param {String} fileId 文件id
   * @return {Promise}
   */
  async getTestCasesOfFile(fileId) {
    const testCases = await tests.getTests({}, { fileId });
    return testCases;
  }

  /**
   * 获取测试用例概要统计
   * @return {Promise}
   */
  getTestCasesSummary() {
    return new Promise((resolve, reject) => {
      const summary = {
        total: 0,
        regression: 0,
        added: 0,
        passed: 0,
        acceptable: 0,
        failed: 0,
      };
      tests.getGroupCount(this.versionId, 'kind')
        .then((data) => {
          data.forEach(({ _id, count }) => {
            summary.total += count;
            if (_id < 0) {
              summary.regression += count;
            } else {
              summary.added += count;
            }
          });
          return tests.getGroupCount(this.versionId, 'resultKind');
        })
        .then((data) => {
          data.forEach(({ _id, count }) => {
            if (FuxinReport.getSucceedResultKind().indexOf(_id) >= 0) {
              summary.passed += count;
            } else {
              summary.failed += count;
            }
          });
          resolve(summary);
        })
        .catch(reject);
    });
  }

  /**
   * 获取测试用例详情
   * @param {Object} testCase 测试用例
   * @param {Object} functionVariables 函数变量信息
   * @param {Object} typeSystem 类型系统
   * @param {Object} i18n 国际化
   * @returns {Object}
   */
  static getTestCaseDetail(testCase, functionVariables, typeSystem, i18n) {
    testCase = tests.getJson(testCase, i18n);
    functionVariables = functions.getFunctionVariableJson(functionVariables);
    typeSystem = files.getTypesJson(testCase.fileId, typeSystem);
    const detail = new ReportTestCaseTransformer(testCase, functionVariables, typeSystem, i18n);
    detail.transform();
    return detail;
  }

  /**
   * 获取文件的测试用例报告数据
   * @param fileId 文件id
   * @return {Promise}
   */
  async getTestCasesReportDataOfFile(fileId) {
    const promises = [
      this.getTypes(fileId),
      this.getFunctionsOfFile(fileId),
      this.getFunctionVariablesOfFile(fileId),
      this.getTestCasesOfFile(fileId),
    ];
    const [types, functions, functionVariables, testCases] = await Promise.all(promises);
    const reportData = {
      functions: [],
      totalNumber: 0,
    };
    // 函数变量
    const functionVariableMap = new Map();
    functionVariables.forEach((functionVariable) => {
      functionVariableMap.set(functionVariable.functionId.toString(), functionVariable);
    });
    // 测试用例
    const testCaseMap = new Map();
    const testType = {};
    testCases.sort((a, b) => {
      if (a.number < b.number) return -1; // eslint-disable-line no-underscore-dangle
      if (a.number > b.number) return 1; // eslint-disable-line no-underscore-dangle
      return 0;
    });
    testCases.forEach((test) => {
      if (FuxinReport.getSucceedResultKind().indexOf(test.resultKind) < 0) {
        // 去除执行未通过的测试用例
        return;
      }
      const functionId = test.functionId.toString();
      if (!testCaseMap.has(functionId)) {
        testCaseMap.set(functionId, []);
      }
      const testArray = testCaseMap.get(functionId);
      const functionVariables = functionVariableMap.get(functionId);
      const testDetails = FuxinReport.getTestCaseDetail(
        test,
        functionVariables,
        types,
        this.i18n
      );

      testDetails.number = test.number.toString();
      while (testDetails.number.length < 3) {
        testDetails.number = `0${testDetails.number}`;
      }
      testDetails.method = (test.userDefinedFields
        .find((udfObj) => udfObj.name === 'test-type') || { value: '' }).value || 'SBT';
      if (testType[testDetails.method] === undefined) {
        testType[testDetails.method] = {
          type: testDetails.method,
          count: 0,
        };
      }
      testType[testDetails.method].count += 1;
      if (this.globalTestTypeStatistics[testDetails.method] === undefined) {
        this.globalTestTypeStatistics[testDetails.method] = 0;
      }
      this.globalTestTypeStatistics[testDetails.method] += 1;
      testArray.push(testDetails);
    });

    functions.forEach((func) => {
      const functionId = func._id.toString(); // eslint-disable-line no-underscore-dangle
      if (!testCaseMap.has(functionId)) {
        return;
      }
      const item = {
        functionName: func.functionName,
        sil: func.sil || '',
        testCases: testCaseMap.get(functionId),
      };
      reportData.functions.push(item);
      reportData.totalNumber += item.testCases.length;
    });
    reportData.testType = Object.values(testType);
    return reportData;
  }

  /**
   * 生成ComponentTestReport测试报告
   * @param {String} outputDir 输出路径
   * @return {Promise}
   */
  generateComponentTestReport(outputDir) {
    return new Promise((resolve, reject) => {
      const fileName = 'Component_Test_Report.xlsx';
      const { projectName } = this.project;
      const { username } = this.creator;
      const outputPath = path.join(outputDir, `${projectName}_${fileName}`);
      this.componentTestReportExcel = new Excel(username);

      FuxinReport.addRevisionsSheet(this.componentTestReportExcel);
      return this.addSummarysSheet(this.componentTestReportExcel)
        .then(() => this.addComponentTestReportFilesSheet())
        .then(() => this.componentTestReportExcel.writeFile(outputPath))
        .then(resolve)
        .catch(reject);
    });
  }

  /**
   * 生成CoverageReviewReport测试报告
   * @param {String} outputDir 输出路径
   * @return {Promise}
   */
  generateCoverageReviewReport(outputDir) {
    return new Promise((resolve, reject) => {
      const fileName = 'CoverageReviewReport.xlsx';
      const { projectName } = this.project;
      const { username } = this.creator;
      const outputPath = path.join(outputDir, `${projectName}_${fileName}`);
      this.coverageReviewReportExcel = new Excel(username);
      const promises = [
        this.getFiles(),
        this.getFunctions(),
      ];
      return Promise.all(promises)
        .then(([files, functions]) => {
          const functionsFilter = new Map();
          functions.forEach((func) => {
            const fileId = func.fileId.toString();
            if (!functionsFilter.has(fileId)) {
              functionsFilter.set(fileId, []);
            }
            functionsFilter.get(fileId).push(func);
          });
          const filesFilter = new Map();
          files.forEach((file) => {
            const fileId = file._id.toString(); // eslint-disable-line no-underscore-dangle
            filesFilter.set(fileId, file);
          });

          const { projectName } = this.project;
          this.coverageReviewReportExcel.addSheet(`${projectName}_CoverageReviewReport`);
          const font = { name: 'Calibri', size: 11 };
          const columns = [
            {
              header: 'File Name',
              key: 'fileName',
              width: 16.93,
              font,
            },
            {
              header: 'Function Name',
              key: 'functionName',
              width: 29.64,
              font,
            },
            {
              header: 'Tester',
              key: 'tester',
              width: 6.64,
              font,
            },
            {
              header: 'New/Regression',
              key: 'kind',
              width: 15.36,
              font,
            },
            {
              header: 'Result',
              key: 'result',
              width: 6.79,
              font,
            },
            {
              header: 'Statement Coverage',
              key: 'statementCoverage',
              width: 18.64,
              font,
            },
            {
              header: 'Decision Coverage',
              key: 'decisionCoverage',
              width: 16.93,
              font,
            },
            {
              header: 'MC/DC Coverage',
              key: 'mcdcCoverage',
              width: 15.64,
              font,
            },
            {
              header: 'Comment',
              key: 'comment',
              width: 25.64,
              font,
            },
            {
              header: 'Reviewer',
              key: 'reviewer',
              width: 9.07,
              font,
            },
            {
              header: 'Review Analysis',
              key: 'reviewAnalysis',
              width: 14.93,
              font,
            },
            {
              header: 'Conclusion',
              key: 'conclusion',
              width: 18.79,
              font,
            },
          ];
          const data = [];
          files.forEach((file) => {
            const fileId = file._id.toString(); // eslint-disable-line no-underscore-dangle
            if (!functionsFilter.has(fileId)) {
              return;
            }
            functionsFilter.get(fileId).forEach((func) => {
              const item = {
                fileName: file.fileName,
                functionName: func.functionName,
                tester: this.creator.username,
                kind: this.version.preVersionId ? 'Regression' : 'New',
                result: func.failedCount > 0 ? 'Failed' : 'Passed',
                statementCoverage: 'none',
                decisionCoverage: 'none',
                mcdcCoverage: 'none',
                conclusion: func.description,
                comment: func.comment,
              };
              let exportState = false;
              if (func.statementTotal) {
                item.statementCoverage = func.statementCovered / func.statementTotal;
                item.statementCoverage = `${Math.floor(item.statementCoverage * 10000) / 100}%`;
                exportState = exportState || item.statementCoverage !== '100%';
              }
              if (func.branchTotal) {
                item.decisionCoverage = func.branchCovered / func.branchTotal;
                item.decisionCoverage = `${Math.floor(item.decisionCoverage * 10000) / 100}%`;
                exportState = exportState || item.decisionCoverage !== '100%';
              }
              if (func.mcdcTotal) {
                item.mcdcCoverage = func.mcdcCovered / func.mcdcTotal;
                item.mcdcCoverage = `${Math.floor(item.mcdcCoverage * 10000) / 100}%`;
                exportState = exportState || item.mcdcCoverage !== '100%';
              }
              // 过滤掉覆盖为100%的函数
              if (exportState) {
                data.push(item);
              }
            });
          });

          this.coverageReviewReportExcel.addRows(columns, data);
          this.coverageReviewReportExcel.setRow(1, { height: 16 });
          ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'].forEach((column) => {
            const options = {
              font: { bold: true },
              fill: {
                type: 'pattern',
                pattern: 'solid',
                fgColor: { argb: 'FFB6D7E4' },
              },
            };
            this.coverageReviewReportExcel.setCell(`${column}1`, options);
            data.forEach((item, index) => {
              this.coverageReviewReportExcel.setCell(`${column}${index + 2}`);
            });
          });
          data.forEach((item, index) => {
            this.coverageReviewReportExcel.setRow(index + 2, { height: 112 });
          });

          return this.coverageReviewReportExcel.writeFile(outputPath);
        })
        .then(resolve)
        .catch(reject);
    });
  }

  /**
   * 生成DynamicTestReport测试报告
   * @param {String} outputDir 输出路径
   * @return {Promise}
   */
  generateDynamicTestReport(outputDir) {
    return new Promise((resolve, reject) => {
      const fileName = 'Dynamic_Test_Report.xlsx';
      const { projectName } = this.project;
      const { username } = this.creator;
      const outputPath = path.join(outputDir, `${projectName}_${fileName}`);
      this.dynamicTestReportExcel = new Excel(username);

      FuxinReport.addRevisionsSheet(this.dynamicTestReportExcel);
      return this.addSummarysSheet(this.dynamicTestReportExcel)
        .then(() => this.addDynamicTestReportFilesSheet())
        .then(() => this.dynamicTestReportExcel.writeFile(outputPath))
        .then(resolve)
        .catch(reject);
    });
  }

  /**
   * 生成SwDDSTraceTable测试报告
   * @param {String} outputDir 输出路径
   * @return {Promise}
   */
  generateSwDDSTraceTableReport(outputDir) {
    return new Promise((resolve, reject) => {
      const fileName = 'SwDDS_TraceTable.xlsx';
      const { projectName } = this.project;
      const { username } = this.creator;
      const outputPath = path.join(outputDir, `${projectName}_${fileName}`);
      this.swDDSTraceTableExcel = new Excel(username);

      const promises = [
        this.getFiles(),
        this.getFunctions(),
        tests.getGroupCount(this.versionId, 'functionId'),
      ];
      return Promise.all(promises)
        .then(([files, functions, testsGroup]) => {
          const functionMap = new Map();
          functions.forEach((func) => {
            const fileId = func.fileId.toString();
            if (!functionMap.has(fileId)) {
              functionMap.set(fileId, []);
            }
            functionMap.get(fileId).push(func);
          });
          const testCaseMap = new Map();
          testsGroup.forEach(({ _id, count }) => {
            if (!count) {
              return;
            }
            const functionId = _id.toString();
            if (!testCaseMap.has(functionId)) {
              testCaseMap.set(functionId, []);
            }
            for (let i = 0; i < count; i += 1) {
              let testNumber = (i + 1).toString();
              while (testNumber.length < 3) {
                testNumber = `0${testNumber}`;
              }
              testCaseMap.get(functionId).push(testNumber);
            }
          });

          const { projectName } = this.project;
          this.swDDSTraceTableExcel.addSheet(`${projectName}_TraceTable`);
          const font = { name: 'Calibri', size: 11 };
          const columns = [
            {
              header: 'File Name',
              key: 'fileName',
              width: 17.64,
              font,
            },
            {
              header: 'Module Name',
              key: 'functionName',
              width: 32.07,
              font,
            },
            {
              header: 'SIL',
              key: 'sil',
              width: 5.46,
              font,
            },
            {
              header: 'SwDDS_ID',
              key: 'requirementId',
              width: 26.36,
              font,
            },
            {
              header: 'TestCase_ID',
              key: 'testCaseId',
              width: 53.5,
              font,
            },
          ];
          const data = [];
          files.forEach((file) => {
            const fileId = file._id.toString(); // eslint-disable-line no-underscore-dangle
            if (!functionMap.has(fileId)) {
              return;
            }
            const { fileName } = file;
            const moduleName = path.basename(fileName, path.extname(fileName));
            functionMap.get(fileId).forEach((func) => {
              const functionId = func._id.toString(); // eslint-disable-line no-underscore-dangle
              if (!testCaseMap.has(functionId)) {
                return;
              }
              testCaseMap.get(functionId).forEach((testCaseNumber) => {
                data.push({
                  fileName,
                  functionName: func.functionName,
                  sil: func.sil,
                  requirementId: func.requirementId,
                  testCaseId: `${func.functionName}_${moduleName}_TC${testCaseNumber}`,
                });
              });
            });
          });
          this.swDDSTraceTableExcel.addRows(columns, data);

          this.swDDSTraceTableExcel.setRow(1, { height: 14 });
          ['A', 'B', 'C', 'D', 'E'].forEach((column) => {
            const options = {
              font: { bold: true },
              fill: {
                type: 'pattern',
                pattern: 'solid',
                fgColor: { argb: 'FFB6D7E4' },
              },
            };
            this.swDDSTraceTableExcel.setCell(`${column}1`, options);
            data.forEach((item, index) => {
              const row = index + 2;
              this.swDDSTraceTableExcel.setRow(row, { height: 28 });
              this.swDDSTraceTableExcel.setCell(`${column}${row}`);
            });
          });
        })
        .then(() => this.swDDSTraceTableExcel.writeFile(outputPath))
        .then(resolve)
        .catch(reject);
    });
  }

  /**
   * 生成测试用例报告
   * @param {String} outputDir 输出路径
   * @return {Promise}
   */
  generateTestCasesReport(outputDir) {
    return new Promise((resolve, reject) => {
      const testCasesDir = path.join(outputDir, 'TestCases');
      fs.ensureDir(testCasesDir)
        .then(() => this.getFiles())
        .then((files) => {
          const that = this;
          function step(index) {
            if (index >= files.length) {
              resolve();
              return;
            }
            that.generateFileTestCasesReport(testCasesDir, files[index])
              .then(() => step(index + 1))
              .catch(() => step(index + 1));
          }
          step(0);
        })
        .catch(reject);
    });
  }

  /**
   * 生成单个文件的测试用例报告
   * @param {String} testCasesDir 测试用例输出文件夹
   * @param {Object} file 文件信息
   * @return {Promise}
   */
  generateFileTestCasesReport(testCasesDir, file) {
    return new Promise((resolve, reject) => {
      const { fileName } = file;
      const fileId = file._id; // eslint-disable-line no-underscore-dangle
      const moduleName = path.basename(fileName, path.extname(fileName));
      const outputPath = path.join(testCasesDir, `${moduleName}_Component_Test_Case.docx`);
      this.getTestCasesReportDataOfFile(fileId)
        .then((reportData) => {
          if (!reportData || reportData.functions.length === 0) {
            return Promise.resolve();
          }
          reportData.moduleName = moduleName;
          reportData.reportTime = moment().format('YYYY-MM-DD');
          reportData.tester = this.creator.username;
          reportData.fileName = fileName;
          const docx = new Docx(
            {
              template: path.join(__dirname, 'Component_Test_Case.template.docx'),
            }
          );
          return docx.generate(reportData, outputPath);
        })
        .then(resolve)
        .catch(reject);
    });
  }

  /**
   * 添加Summarys工作表
   * @param {Excel} excel 表
   * @return {Promise}
   */
  addSummarysSheet(excel) {
    return new Promise((resolve, reject) => {
      excel.addSheet('Summarys');
      const titleFont = { name: 'Times New Roman', size: 20, bold: true };
      const contentFont = { name: 'Times New Roman', size: 12 };

      const options = {
        value: 'Number of Regression TestCase',
        font: titleFont,
        fill: {
          type: 'pattern',
          pattern: 'solid',
          fgColor: { argb: 'FFC0C0C0' },
        },
      };
      excel.setCell('B2', options);
      excel.mergeCells('B2', 'G2');
      excel.setRow(2, { height: 36 });
      excel.setColumn('A', { width: 8.17 });
      excel.setColumn('B', { width: 30.17 });
      excel.setColumn('C', { width: 11.33 });
      excel.setColumn('D', { width: 11.33 });
      excel.setColumn('E', { width: 17.67 });
      excel.setColumn('F', { width: 16.17 });
      excel.setColumn('G', { width: 16.17 });
      this.getTestCasesSummary()
        .then((summary) => {
          const { projectName } = this.project;
          [
            '',
            'Number of Regression TestCase',
            'Number of New TestCase',
            'Number of Pass TestCase',
            'Number of Acceptable TestCase',
            'Number of  Fail TestCase',
          ].forEach((field, index) => {
            const options = {
              value: field,
              font: contentFont,
              fill: {
                type: 'pattern',
                pattern: 'solid',
                fgColor: { argb: 'FFD7FDD0' },
              },
            };
            excel.setCell(`B${index + 3}`, options);
          });
          [
            projectName,
            summary.regression,
            summary.added,
            summary.passed,
            summary.acceptable,
            summary.failed,
          ].forEach((value, index) => {
            const row = index + 3;
            const options = {
              value,
              font: JSON.parse(JSON.stringify(contentFont)),
              fill: {
                type: 'pattern',
                pattern: 'solid',
                fgColor: { argb: 'FFFFFDA6' },
              },
            };
            options.font.color = { argb: 'FF4771F1' };
            excel.setCell(`C${row}`, options);
            excel.mergeCells(`C${row}`, `G${row}`);
            excel.setRow(row, { height: 16 });
          });
          resolve();
        })
        .catch(reject);
    });
  }

  /**
   * 添加ComponentTestReport报告中的文件集合工作表
   * @return {Promise}
   */
  addComponentTestReportFilesSheet() {
    return new Promise((resolve, reject) => {
      this.getFiles()
        .then((resFiles) => {
          const that = this;
          function step(index) {
            if (index >= resFiles.length) {
              resolve();
              return;
            }
            that.addComponentTestReportFileSheet(resFiles[index])
              .then(() => {
                step(index + 1);
              })
              .catch(() => {
                step(index + 1);
              });
          }
          step(0);
        })
        .catch(reject);
    });
  }

  /**
   * 添加ComponentTestReport报告中的单个文件工作表
   * @param {File} file 文件
   * @return {Promise}
   */
  addComponentTestReportFileSheet(file) {
    return new Promise((resolve, reject) => {
      const fileId = file._id; // eslint-disable-line no-underscore-dangle
      const { fileName } = file;
      const promises = [
        this.getFunctionsOfFile(fileId),
        this.getTestCasesOfFile(fileId),
      ];
      return Promise.all(promises)
        .then(([functions, testcases]) => {
          if (testcases.length === 0) {
            resolve();
            return;
          }
          const testsFilter = new Map();
          testcases.forEach((test) => {
            const functionId = test.functionId.toString();
            if (!testsFilter.has(functionId)) {
              testsFilter.set(functionId, []);
            }
            testsFilter.get(functionId).push(test);
          });
          const font = { name: 'Calibri', size: 11 };
          const columns = [
            {
              header: 'No',
              key: 'number',
              width: 3.5,
              font,
            },
            {
              header: 'Test Case',
              key: 'name',
              width: 50.17,
              font,
            },
            {
              header: 'New/Regression',
              key: 'kind',
              width: 15.17,
              font,
            },
            {
              header: 'Result',
              key: 'result',
              width: 6.67,
              font,
            },
            {
              header: 'Comment',
              key: 'comment',
              width: 9.17,
              font,
            },
            {
              header: 'Tester',
              key: 'tester',
              width: 6.5,
              font,
            },
          ];
          const data = [{}];
          let number = 0;
          functions.forEach((func) => {
            const functionId = func._id.toString(); // eslint-disable-line no-underscore-dangle
            if (!testsFilter.has(functionId)) {
              return;
            }
            testsFilter.get(functionId).forEach((test, index) => {
              number += 1;
              let testNumber = (index + 1).toString();
              while (testNumber.length < 3) {
                testNumber = `0${testNumber}`;
              }
              data.push({
                number,
                name: `${func.functionName}_${this.project.projectName}_TC${testNumber}`,
                kind: test.kind < 0 ? 'Regression' : 'New',
                result: FuxinReport.getSucceedResultKind().indexOf(test.resultKind) >= 0 ? 'Passed' : 'Failed',
                tester: this.creator.username,
              });
            });
          });

          const sheetName = path.basename(fileName, path.extname(fileName));
          this.componentTestReportExcel.addSheet(sheetName);
          this.componentTestReportExcel.addRows(columns, data);

          ['A', 'B', 'C', 'D', 'E', 'F'].forEach((column) => {
            const options = {
              font: { bold: true },
              fill: {
                type: 'pattern',
                pattern: 'solid',
                fgColor: { argb: 'FFB6D7E4' },
              },
            };
            this.componentTestReportExcel.setCell(`${column}1`, options);
          });
          const fileRowOptions = {
            value: fileName,
            fill: {
              type: 'pattern',
              pattern: 'solid',
              fgColor: { argb: 'FFD3D3D3' },
            },
          };
          this.componentTestReportExcel.setCell('A2', fileRowOptions);
          this.componentTestReportExcel.mergeCells('A2', 'F2');
          resolve();
        })
        .catch(reject);
    });
  }

  /**
   * 添加DynamicTestReport报告中的文件集合工作表
   * @return {Promise}
   */
  addDynamicTestReportFilesSheet() {
    return new Promise((resolve, reject) => {
      this.getFiles()
        .then((resFiles) => {
          const that = this;
          function step(index) {
            if (index >= resFiles.length) {
              resolve();
              return;
            }
            that.addDynamicTestReportFileSheet(resFiles[index])
              .then(() => {
                step(index + 1);
              })
              .catch(() => {
                step(index + 1);
              });
          }
          step(0);
        })
        .catch(reject);
    });
  }

  /**
   * 添加DynamicTestReport报告中的单个文件工作表
   * @param {File} file 文件
   * @return {Promise}
   */
  addDynamicTestReportFileSheet(file) {
    return new Promise((resolve, reject) => {
      const fileId = file._id; // eslint-disable-line no-underscore-dangle
      const { fileName } = file;
      const promises = [
        this.getFunctionsOfFile(fileId),
        this.getTestCasesOfFile(fileId),
      ];
      return Promise.all(promises)
        .then(([functions, testcases]) => {
          if (testcases.length === 0) {
            resolve();
            return;
          }
          const testsFilter = new Map();
          testcases.forEach((test) => {
            const functionId = test.functionId.toString();
            if (!testsFilter.has(functionId)) {
              testsFilter.set(functionId, []);
            }
            testsFilter.get(functionId).push(test);
          });
          const font = { name: 'Calibri', size: 11 };
          const columns = [
            {
              header: 'File Name',
              key: 'fileName',
              width: 13.36,
              font,
            },
            {
              header: 'Function Name',
              key: 'functionName',
              width: 31.86,
              font,
            },
            {
              header: 'Tester',
              key: 'tester',
              width: 6.69,
              font,
            },
            {
              header: 'New/Regression',
              key: 'kind',
              width: 15.36,
              font,
            },
            {
              header: 'Result',
              key: 'result',
              width: 6.86,
              font,
            },
            {
              header: 'Statement Coverage',
              key: 'statementCoverage',
              width: 18.69,
              font,
            },
            {
              header: 'Decision Coverage',
              key: 'decisionCoverage',
              width: 16.86,
              font,
            },
            {
              header: 'MC/DC Coverage',
              key: 'mcdcCoverage',
              width: 15.86,
              font,
            },
            {
              header: 'Comment',
              key: 'comment',
              width: 9.36,
              font,
            },
            {
              header: 'Test Case',
              key: 'testCase',
              width: 50.36,
              font,
            },
          ];
          const data = [];
          functions.forEach((func) => {
            const functionId = func._id.toString(); // eslint-disable-line no-underscore-dangle
            if (!testsFilter.has(functionId)) {
              return;
            }
            testsFilter.get(functionId).forEach((test, index) => {
              let testNumber = (index + 1).toString();
              while (testNumber.length < 3) {
                testNumber = `0${testNumber}`;
              }
              const item = {
                fileName,
                functionName: func.functionName,
                tester: this.creator.username,
                kind: this.version.preVersionId ? 'Regression' : 'New',
                result: func.failedCount > 0 ? 'Failed' : 'Passed',
                statementCoverage: 'none',
                decisionCoverage: 'none',
                mcdcCoverage: 'none',
                testCase: `${func.functionName}_${this.project.projectName}_TC${testNumber}`,
              };
              if (func.statementTotal) {
                item.statementCoverage = func.statementCovered / func.statementTotal;
                item.statementCoverage = `${Math.floor(item.statementCoverage * 10000) / 100}%`;
              }
              if (func.branchTotal) {
                item.decisionCoverage = func.branchCovered / func.branchTotal;
                item.decisionCoverage = `${Math.floor(item.decisionCoverage * 10000) / 100}%`;
              }
              if (func.mcdcTotal) {
                item.mcdcCoverage = func.mcdcCovered / func.mcdcTotal;
                item.mcdcCoverage = `${Math.floor(item.mcdcCoverage * 10000) / 100}%`;
              }

              data.push(item);
            });
          });

          const sheetName = path.basename(fileName, path.extname(fileName));
          this.dynamicTestReportExcel.addSheet(sheetName);
          this.dynamicTestReportExcel.addRows(columns, data);

          this.dynamicTestReportExcel.setRow(1, { height: 28 });
          ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'].forEach((column) => {
            const options = {
              font: { bold: true },
              fill: {
                type: 'pattern',
                pattern: 'solid',
                fgColor: { argb: 'FFB6D7E4' },
              },
            };
            this.dynamicTestReportExcel.setCell(`${column}1`, options);
            data.forEach((item, index) => {
              this.dynamicTestReportExcel.setCell(`${column}${index + 2}`);
            });
            let startIndex = 2;
            functions.forEach((func) => {
              if (column === 'A' || column === 'J') {
                return;
              }
              const functionId = func._id.toString(); // eslint-disable-line no-underscore-dangle
              if (!testsFilter.has(functionId)) {
                return;
              }
              const testNum = testsFilter.get(functionId).length;
              this.dynamicTestReportExcel.mergeCells(`${column}${startIndex}`, `${column}${startIndex + testNum - 1}`);
              startIndex += testNum;
            });
          });
          data.forEach((item, index) => {
            this.dynamicTestReportExcel.setRow(index + 2, { height: 28 });
          });
          this.dynamicTestReportExcel.mergeCells('A2', `A${data.length + 1}`);
          resolve();
        })
        .catch(reject);
    });
  }

  /**
   * 添加Revisions工作表
   * @param {Excel} excel 表
   */
  static addRevisionsSheet(excel) {
    excel.addSheet('Revisions');
    const revisionsFont = { name: 'Times New Roman', size: 20, bold: true };
    const revisionsTitleFont = { name: 'Times New Roman', size: 12, bold: true };
    excel.setCell('A1', { value: 'Revisions', font: revisionsFont });
    excel.mergeCells('A1', 'K1');
    excel.setRow(1, { height: 26 });

    excel.setCell('A2', { value: 'No.', font: revisionsTitleFont });
    excel.setCell('C2', { value: 'Start time', font: revisionsTitleFont });
    excel.setCell('E2', { value: 'End time', font: revisionsTitleFont });
    excel.setCell('G2', { value: 'Previous Version', font: revisionsTitleFont });
    excel.setCell('J2', { value: 'Updated Version', font: revisionsTitleFont });
    excel.setColumn('A', { width: 8.33 });
    excel.setColumn('B', { width: 8.33 });
    excel.setColumn('C', { width: 8.33 });
    excel.setColumn('D', { width: 8.33 });
    excel.setColumn('E', { width: 8.33 });
    excel.setColumn('F', { width: 8.33 });
    excel.setColumn('G', { width: 8.33 });
    excel.setColumn('H', { width: 8.33 });
    excel.setColumn('I', { width: 42.67 });
    excel.setColumn('J', { width: 8.33 });
    excel.setColumn('K', { width: 53.5 });
    for (let i = 2; i < 10; i += 1) {
      excel.setRow(i, { height: 70.5 });
      excel.mergeCells(`A${i}`, `B${i}`);
      excel.mergeCells(`C${i}`, `D${i}`);
      excel.mergeCells(`E${i}`, `F${i}`);
      excel.mergeCells(`G${i}`, `I${i}`);
      excel.mergeCells(`J${i}`, `K${i}`);
    }
  }

  static getSucceedResultKind() {
    return [tests.resultKind.succeed, tests.resultKind.nonStrictSucceed];
  }
}

/**
 * Export model definition object.
 */
module.exports = FuxinReport;
